import json
import requests
import urllib.parse as urlparse
import datetime
import time
import hmac
import base64
import hashlib
import sys

from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin2.api_client.common import (SyncQueryer)
from enum import Enum

class Side(Enum):
  BID = "BID"
  ASK = "ASK"

class Type(Enum):
  LIMIT = "LIMIT"
  MARKET = "MARKET"
  STOP = "STOP"
  STOP_LIMIT = "STOP_LIMIT"

class TimeInForce(Enum):
  FILL_OR_KILL = "FILL_OR_KILL"
  IMMEDIATE_OR_CANCEL = "IMMEDIATE_OR_CANCEL"
  GOOD_FOR_DAY = "GOOD_FOR_DAY"
  GOOD_TIL_CANCELLED = "GOOD_TIL_CANCELLED"

class TriggerMethod(Enum):
  ONE_TOUCH = "ONE_TOUCH"
  BID_OFFER = "BID_OFFER"

class LmaxAuth(requests.auth.AuthBase):
  def __init__(self, key):
    with open(key,"r") as f:
      text = f.read()
    j = json.loads(text)
    self.access_key = j['rest_access_key']
    self.secret_key = j['rest_secret_key']

    assert self.access_key, "self.access_key must be not None and not empty!"
    assert self.secret_key, "self.secret_key must be not None and not empty!"

    self.token = None

  def _gen_signature(self, nonce: str, timestamp: str):
    assert nonce, "nonce must be not None and not empty!"
    assert timestamp, "timestamp must be not None and not empty!"

    client_secret = base64.b64decode(self.secret_key)
    before_signature = (self.access_key + nonce + timestamp).encode()

    signature = hmac.new(client_secret, before_signature, digestmod = hashlib.sha256).digest()

    return base64.b64encode(signature).decode()

  def __call__(self, req: requests.PreparedRequest):
    parsed = urlparse.urlparse(req.url)

    if parsed.path == "/v1/authenticate":
      timestamp = f"{datetime.datetime.utcnow().isoformat()[:-3]}Z"
      # nonce in microsecond
      nonce = str(int(time.time() * 1000 * 1000))
      signature = self._gen_signature(nonce, timestamp)

      not_none_json = {
        'client_key_id': self.access_key,
        'timestamp': timestamp,
        'nonce': nonce,
        'signature': signature
      }

      req.prepare_body(None, None, json=not_none_json)

      # print(req.headers)
      # print(req.body)
      # print(req.url)

      return req

    assert self.token, "token empty, please call authentication before other api!"
    req.headers.update({'Authorization': "Bearer {}".format(self.token)})
    req.prepare_headers(req.headers)

    if req.body:
      filter_before = json.loads(req.body)
      # filter because server don't ignore null value
      not_none_json = {k:v for k,v in filter_before.items() if v is not None}

      req.prepare_body(None, None, json=not_none_json)

    # print(req.headers)
    # print(req.body)
    # print(req.url)

    return req

class LmaxPrivateClient():
  def __init__(self, key_path: str, ip=None):
    self._auth = LmaxAuth(key_path)
    self._url = "https://account-api.london-digital.lmax.com"

    self._query = SyncQueryer(api_host=self._url, auth=self._auth, ip=ip)

  def authenticate(self):
    method = "POST"
    path = "/v1/authenticate"

    response = self._query.query(method=method, path=path)
    json = response.json()

    self._auth.token = json['token']

    return json

  def get_open_order(self):
    method = "GET"
    path = "/v1/account/working-orders"

    response = self._query.query(method=method, path=path)
    return response.json()

  def cancel_order(
    self,
    instrument_id: str,
    cancel_instruction_id: str,
    instruction_id: str,
  ):
    assert instrument_id, "instrument_id must be not None and not empty!"
    assert cancel_instruction_id, "cancel_instruction_id must be not None and not empty!"
    assert instruction_id, "instruction_id must be not None and not empty!"

    method = "POST"
    json = {
      "instrument_id": instrument_id,
      "cancel_instruction_id": cancel_instruction_id,
      "instruction_id": instruction_id,
    }
    path = "/v1/account/cancel-order"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

  def submit_order(
    self,
    instrument_id: str,
    type: Type,
    side: Side,
    quantity: str,
    instruction_id: str,
    time_in_force: TimeInForce,
    price: str = None,
    stop_price: str = None,
    trigger_method: TriggerMethod = None,
  ):
    assert instrument_id, "instrument_id must be not None and not empty!"
    assert type, "type must be not None!"
    assert side, "side must be not None!"
    assert quantity, "quantity must be not None and not empty!"
    assert instruction_id, "instruction_id must be not None and not empty!"
    assert time_in_force, "time_in_force must be not None!"

    method = "POST"
    json = {
      "instrument_id": instrument_id,
      "type": type.value,
      "side": side.value,
      "quantity": quantity,
      "instruction_id": instruction_id,
      "time_in_force": time_in_force.value,
      "price": price,
      "stop_price": stop_price,
      "trigger_method": trigger_method and trigger_method.value,
    }
    path = "/v1/account/place-order"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

# api doc url
# https://docs.lmax.com/

if __name__ == "__main__":
  key_path = sys.argv[1]
  
  lmax = LmaxPrivateClient(key_path)
  info = lmax.authenticate()
  info = lmax.get_open_order()
  # info = lmax.cancel_order('eur-usd', 'cancel', 'instruction-1254')
  # info = lmax.submit_order("eur-usd", Type.LIMIT, Side.ASK, "0.0000001", "instruction-1254", TimeInForce.GOOD_TIL_CANCELLED, "100000000")
  print(json.dumps(info, indent=2))
